package driver

import (
	"errors"
	"strconv"
	"strings"

	// "bytes"
	"fmt"
	"io"

	// "io/ioutil"
	"log"
	"math"
	"os"

	"github.com/pkg/sftp"
	"golang.org/x/crypto/ssh"
)

const once_read_max_size = 10 * 1024 * 1024

type ssh_driver struct {
	host string
	user string
	pass string
}

func (s *ssh_driver) parse(args string) {
	fmt.Sscanf(args, "%s%s", &s.user, &s.pass)
	log.Println("Parse", s.host, s.user, s.pass)
}

func (s *ssh_driver) CreateHandle() Handle {
	handle := &ssh_handle{
		host: s.host,
		user: s.user,
		pass: s.pass,
	}

	err := handle.open()
	if err != nil {
		return nil
	}
	return handle
}

type ssh_handle struct {
	host string
	user string
	pass string

	client  *ssh.Client
	sftp    *sftp.Client
	session *ssh.Session
}

func (h *ssh_handle) open() error {
	config := &ssh.ClientConfig{
		User: h.user,
		Auth: []ssh.AuthMethod{
			ssh.Password(h.pass),
		},
		HostKeyCallback: ssh.InsecureIgnoreHostKey(), // 忽略主机密钥
	}
	var err error
	h.client, err = ssh.Dial("tcp", h.host, config)
	return err
}

func (h *ssh_handle) EnableFileTransport() error {
	if h.client == nil {
		return errors.New("Connect Not Open")
	}
	var err error
	h.sftp, err = sftp.NewClient(h.client)
	return err
}

func (h *ssh_handle) Close() error {
	if h.sftp != nil {
		h.sftp.Close()
		h.sftp = nil
	}

	if h.client != nil {
		h.client.Close()
		h.client = nil
	}
	return nil
}

func (h *ssh_handle) Mkdir(dir string) error {
	return h.run_cmd(fmt.Sprintf("mkdir -p %v", dir))
}

func (h *ssh_handle) run_cmd(cmd string) error {
	// fmt.Println(cmd)
	session, err := h.client.NewSession()
	if err != nil {
		return err
	}
	defer session.Close()
	_, err = session.CombinedOutput(cmd)
	if err != nil {
		return err
	}
	return nil
}

func (h *ssh_handle) run_cmd2(cmd string) (string, error) {
	// fmt.Println(cmd)
	session, err := h.client.NewSession()
	if err != nil {
		return "", err
	}
	defer session.Close()
	out, err2 := session.CombinedOutput(cmd)
	return string(out), err2
}

func (h *ssh_handle) PushFile(local, remote string) error {
	if h.sftp == nil {
		return errors.New("No Call EnableFileTransport")
	}
	file, err := os.Open(local)
	if nil != err {
		return err
	}
	defer file.Close()
	ftpFile, err := h.sftp.Create(remote)
	if nil != err {
		return err
	}
	defer ftpFile.Close()

	once_read_max_size := 10 * 1024 * 1024
	file_stat, _ := file.Stat()
	file_size := file_stat.Size()
	once_read_size := int64(math.Min(float64(file_size), float64(once_read_max_size)))
	buff := make([]byte, once_read_size)

	for {
		size, rerr := file.Read(buff)
		if rerr == io.EOF || size == 0 {
			if size > 0 {
				ftpFile.Write(buff[0:size])
			}
			break
		} else if err != nil {
			return err
		}
		ftpFile.Write(buff[0:size])
	}
	return nil
}

func (h *ssh_handle) UnTarGZ(file, to string) error {
	return h.run_cmd(fmt.Sprintf("tar -zxf %v -C %v", file, to))
}

func (h *ssh_handle) UnTar(file, to string) error {
	return h.run_cmd(fmt.Sprintf("tar -xf %v -C %v", file, to))
}

func (h *ssh_handle) RmFile(file string) error {
	return h.run_cmd(fmt.Sprintf("rm -fr %v", file))
}

func (h *ssh_handle) AddExe(file string) error {
	return h.run_cmd(fmt.Sprintf("chmod +x %v", file))
}

func (h *ssh_handle) WriteFile(file string, content []byte) error {
	if h.sftp == nil {
		return errors.New("No Call EnableFileTransport")
	}
	ftpFile, err := h.sftp.Create(file)
	if nil != err {
		return err
	}
	defer ftpFile.Close()
	ftpFile.Write(content)
	return nil
}

func (h *ssh_handle) ReadFile(file string) ([]byte, error) {
	if h.sftp == nil {
		return nil, errors.New("No Call EnableFileTransport")
	}
	ftpFile, err := h.sftp.Open(file)
	if nil != err {
		return nil, err
	}
	defer ftpFile.Close()
	out := make([]byte, 0)
	buff := make([]byte, 1024)
	for {
		n, err := ftpFile.Read(buff)
		if err == io.EOF || n == 0 {
			if n > 0 {
				out = append(out, buff[0:n]...)
			}
			break
		} else if err != nil {
			return nil, err
		}

		out = append(out, buff[0:n]...)
	}
	return out, nil
}

func (h *ssh_handle) IsExist(file string) (bool, error) {
	out, err := h.run_cmd2(fmt.Sprintf("ls -la %v 2> /dev/null | wc -l", file))
	if err != nil {
		return false, err
	}
	n, err2 := strconv.Atoi(strings.Trim(out, " \r\n\t"))
	if err2 != nil {
		return false, err2
	}
	return n != 0, nil
}

func (h *ssh_handle) Ln(to, from string) error {
	return h.run_cmd(fmt.Sprintf("rm -fr %v && ln -fs %v %v", from, to, from))
}

func (h *ssh_handle) Execute(cmd string) (string, error) {
	return h.run_cmd2(cmd)
}

func new_ssh_driver(host string, port int, args string) Driver {
	log.Println("new ssh", args)
	ret := &ssh_driver{}
	if port == 0 {
		port = 22
	}
	ret.host = fmt.Sprintf("%v:%v", host, port)
	ret.parse(args)
	return ret
}

func init() {
	log.Println("init ssh_driver")
	driver_set["ssh"] = driver_slot{
		create:  new_ssh_driver,
		destroy: func(Driver) {},
	}
}
